using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Xml.Schema;
using Baik.Interface.Execution;
using Baik.Interface.Framework;

namespace Baik.Execution
{
    public class SystemObject : BuiltInObject, ISystem
    {
        public IExecutionEnvironment Exec;
        public Dictionary<string, string> rego = new Dictionary<string, string>();
        public FileHandler File;
        public IExceptionThrower Thrower;
        public IInjector Inject { get; private set; }
        public SystemObject(IInjector inject, IExecutionEnvironment exec) : base(inject)
        {
            Inject = inject;
            Exec = exec;
            Thrower = inject.Instantiate<IExceptionThrower>();
            File = new FileHandler(inject);
            Dictionary.Add("load",  BuiltInFactory.Function(inject, LoadFile));
            Dictionary.Add("loadExtension",  BuiltInFactory.Function(inject, LoadExtension));
            Dictionary.Add("write",  BuiltInFactory.Function(inject, Write));
            Dictionary.Add("refresh",  BuiltInFactory.Function(inject, Refresh));
            Dictionary.Add("throw",  BuiltInFactory.Function(inject, ThrowCodeException));
            Dictionary.Add("registry", new Registry(Inject));
            
        }

        private IObject Write(IEnumerable<IObject> arg)
        {
            var arr = arg as IObject[] ?? arg.ToArray();
            if(arr.Count() < 2 || !(arr[0] is IString)) Thrower.Throw("Must pass a string and an Object");
            System.IO.File.WriteAllText(((IString) arr[0]).Value, arr[1].AsString());
            return arr[1];
        }
        private IObject ThrowCodeException(IEnumerable<IObject> arg)
        {   
            var args = arg.ToArray();
            if (args.Length < 0) Thrower.Throw("UNSPECIFIED EXCEPTION");
            else if (args[0] is IString) Thrower.Throw(((IString) args[0]).Value);
            else Thrower.Throw(args[0]);
            return this;
        }
        private IObject Refresh(IEnumerable<IObject> arg)
        {
            Exec.Refresh();
            return this;
        }
        private IObject LoadExtension(IEnumerable<IObject> arg)
        {
            arg.OfType<IString>()
                .ToList()
                .Select(istr => Assembly.LoadFrom(istr.Value))
                .ToList()
                .ForEach(
                    a =>
                        a.GetTypes()
                            .Where(t => typeof (IRegistrationScript).IsAssignableFrom(t))
                            .ToList()
                            .ForEach(rs => Inject.Run((IRegistrationScript) Activator.CreateInstance(rs))));
            return this;
        }
        private IObject LoadFile(IEnumerable<IObject> arg)
        {
            var enumerable = arg as IObject[] ?? arg.ToArray();
            if (enumerable.Length < 1) Thrower.Throw("you must pass a string parameter to the load function");
            if (!(enumerable[0] is IString)) Thrower.Throw("you must pass a string parameter to the load function");
            return Exec.Load(((IString) enumerable.ToArray()[0]).Value);
        }
        public class FileHandler : BuiltInObject
        {
            public FileHandler(IInjector inject) : base(inject)
            {
            }

            public string Load(string filepath)
            {
                throw new NotImplementedException();
            }
        }
        public class Registry : BuiltInObject
        {
            public Registry(IInjector inject) : base(inject)
            {
                    
            }
        }
    }
    public class LanguageException : Exception, IExceptionHost
    {
        public IObject Exception { get; set; }
    }

    public class ExceptionThrower : IExceptionThrower
    {
        public IInjector Inject { get; private set; }
        public ExceptionThrower(IInjector inject)
        {
            Inject = inject;
        }

        public void Throw(string message)
        {
            var rtn = Inject.Instantiate<IObject>();
            var str = Inject.Instantiate<IString>();
            str.Value = message;
            rtn.Assign("msg", str);
            this.Throw(rtn);
        }

        public void Throw(IObject obj)
        {
            var rtn = Inject.Instantiate<IExceptionHost>();
            rtn.Exception = obj;
            throw (Exception) rtn;
        }
    }
}